home *** CD-ROM | disk | FTP | other *** search
/ Aminet 15 / Aminet 15 - Nov 1996.iso / Aminet / biz / dopus / comparesource.lha / compare_source / source / compare.module.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-10-03  |  40.3 KB  |  1,111 lines

  1. /*######################################################################################
  2. ## compare.module by Leo 'Nudel' Davidson for Gods'Gift Utilities.                      ##
  3. ## A plug-in module for Directory Opus 5.5 to compare the contents of two files.      ##
  4. ##                                                                                      ##
  5. ##                                                                                      ##
  6. ## Until July 1998 you should be able to contact me via any of the following:          ##
  7. ##                                                                                      ##
  8. ## email: leo.davidson@keble.oxford.ac.uk                                              ##
  9. ##   www: http://users.ox.ac.uk/~kebl0364                                              ##
  10. ##   IRC: Nudel in #Amiga on Effnet or Undernet (very rarely).                          ##
  11. ##                                                                                      ##
  12. ## Comments, suggestions, questions, offers, and chats all welcome.                      ##
  13. ##                                                                                      ##
  14. ##                                                                                      ##
  15. ## Tabsize: 4 -- 88 Columns (sorry) -- Amiga-specific -- Compile with SAS/C.          ##
  16. ##                                                                                      ##
  17. ## Credit is due to Nick Christie, Jonathan Potter, and Greg Perry for their advice,  ##
  18. ## examples, and general help beyond the call of duty. Thanks guys!                      ##
  19. ##************************************************************************************##
  20. ## There appears to be a bug in DOpus 5.5: Each call to AsyncRequestTags() looses      ##
  21. ## 32 bytes of memory. This also happens in the example module which comes with the      ##
  22. ## OpusSDK and has been reported to GPSoftware.                                          ##
  23. ########################################################################################
  24. ## This program rarely allocates more than about 30k at a time. Since all error          ##
  25. ## messages are output by requesters (which take quite a bit of mem to display),      ##
  26. ## memory allocation failures result in a silent abort. Perhaps it would be better      ##
  27. ## to at least call DisplayBeep() -- maybe in the future.                              ##
  28. ## In the places where large allocations are possible (while generating the full      ##
  29. ## report, mostly), error requesters ARE implimented.                                  ##
  30. ########################################################################################
  31. ## I almost added the option to specify the files to compare on the command-line, but ##
  32. ## I can't see any point or use for it.                                                  ##
  33. ######################################################################################*/
  34.  
  35. #include "compare.module.h"
  36.  
  37. /**************************************************************************************/
  38.  
  39. #define PROGNAME "compare.module"
  40. #define PROGVERS "1.1"
  41. #define PROGDATE __AMIGADATE__
  42. static char version_str[] = "\0$VER: " PROGNAME " " PROGVERS " " PROGDATE "\0";
  43. // Above line should be double null-terminated.
  44.  
  45. /*= Definition of the module =========================================================*/
  46. ModuleInfo module_info =
  47. {
  48.         1,                                    // Version
  49.         "compare.module",                    // Module name
  50.         "compare.catalog",                    // Catalog name
  51.         0,                                    // Flags
  52.         1,                                    // Number of functions
  53.         {0,"Compare",MSG_COMPARE_DESC,\
  54.          FUNCF_NEED_SOURCE|FUNCF_NEED_FILES|FUNCF_SINGLE_SOURCE|FUNCF_SINGLE_DEST|\
  55.          FUNCF_WANT_DEST,\
  56.          0}                                    // First function.
  57. };
  58.  
  59. /**************************************************************************************/
  60.  
  61. /*= L_Module_Entry() =================================================================-.
  62. || Main entry point to the module. The L_ is to identify this as a library              ||
  63. || function (specified by the "libprefix" option in the makefile)                      ||
  64. ||------------------------------------------------------------------------------------||
  65. || This kinda evolved into one huge routine and at this stage isn't really worth      ||
  66. || cutting down. I'd definately split things up if I did it all again, though.          ||
  67. `-====================================================================================*/
  68. int __asm __saveds L_Module_Entry(
  69.     register __a0 char *args,                // User-supplied arguments
  70.     register __a1 struct Screen *screen,    // Screen to open on
  71.     register __a2 IPCData *ipc,                // Our IPC pointer
  72.     register __a3 IPCData *main_ipc,        // Main Opus IPC pointer
  73.     register __d0 ULONG mod_id,                // ID of module function
  74.     register __d1 EXT_FUNC(func_callback))    // Opus callback function
  75. {
  76.     Compare_Data *data;                // Pseudo-global variables.
  77.     LONG diffbytes;                    // Number of differences between the files.
  78.  
  79.     if (data = AllocVec(sizeof(Compare_Data),MEMF_CLEAR))
  80.     {
  81.         data->ipc = ipc;
  82.         data->func_callback = func_callback;
  83.  
  84.         if (data->rnd.poolhead = \
  85.             NewMemHandle(PUDDLESIZE,THRESHSIZE,MEMF_CLEAR) )
  86.         {
  87.             data->rnd.data = data;
  88.             if (DOpusBase->lib_Version < MIN_OPUS_VERSION)
  89.                 informUser(data,dgs(MSG_VERSREQ_FMT),FALSE,NULL,MIN_OPUS_VERSION);
  90.             else
  91.             {
  92.                 if ((get_files_to_compare(data,data->file1path,data->file1name,
  93.                                                data->file2path,data->file2name)) \
  94.                     && (data->file1rn = createResNode(&data->rnd)) \
  95.                     && (data->file2rn = createResNode(&data->rnd)) \
  96.                     && (open_files_to_compare(data)) \
  97.                     && ( (diffbytes = compare_files(data)) >= 0 ) )
  98.                 {
  99.                     // Note: The files being compared remain open for later.
  100.                     //       (They'll be closed by ResNodes automatically on abort.)
  101.  
  102.                     // Report the number of differences, and, if there were any, ask
  103.                     // if the user wants a full display of them.
  104.                     if (report_num_diffs(data,diffbytes))
  105.                     {
  106.                         // If they asked for a full display, generate one.
  107.                         report_full_display(data);
  108.                     }
  109.                     // If more is done before the call to deleteAllResNodes()
  110.                     // just below, file1rn and file2rn should be deleted unless
  111.                     // the files are still wanted (remember they are still open).
  112.                 }
  113.             }
  114.             // Free all remainding resources allocated with ResNodes.
  115.             deleteAllResNodes(&data->rnd);    // Safe to call even if no ResNodes.
  116.  
  117.             // Free our memory pool.
  118.             FreeMemHandle(data->rnd.poolhead);
  119.             data->rnd.poolhead = NULL;
  120.         }
  121.         // Free our pseudo-global variables.
  122.         FreeVec(data);
  123.     }
  124.     // Currently, functions should always return 1.
  125.     return(1);
  126. }
  127.  
  128. /*= InformUser() =====================================================================-.
  129. || Send the user a requester with printf-style formatted text.                          ||
  130. || Requester is centered on the Opus screen and has just an "OK" gadget.              ||
  131. || If window is TRUE it'll try to open over the lister window.                              ||
  132. ||------------------------------------------------------------------------------------||
  133. || Flags: IU_DISPLAY -- changes gadgets to "Display" and "OK" and returns 1 or 0 resp.||
  134. `-====================================================================================*/
  135. BOOL informUser(Compare_Data *data,char *format,BOOL window,ULONG flags,...)
  136. {
  137.     BOOL iu_return;
  138.     struct Window *win;
  139.     struct Screen *screen;
  140.     ResNode *rn1;
  141.     va_list  ap;
  142.  
  143.     if (rn1 = allocNewResNode(&data->rnd,INFORMUSERBUFFERSIZE))
  144.     {
  145.         // Build requester text
  146.         va_start(ap,flags);
  147.         vsprintf(rn1->rn_Mem,format,ap);
  148.         va_end(ap);
  149.  
  150.         if (window)
  151.         {
  152.             win = getListerWindow(data);
  153.             screen = NULL;
  154.         }
  155.         else
  156.         {
  157.             screen = getDOpusScreen(data);
  158.             win = NULL;
  159.         }
  160.  
  161.         // Display requester over window.
  162.         iu_return = AsyncRequestTags(data->ipc, REQTYPE_SIMPLE, 0, 0, 0,
  163.             TAGIF(win,AR_Window),    win,
  164.             TAGIF((!win) && screen,AR_Screen),    screen,
  165.             AR_Message,                rn1->rn_Mem,
  166.             AR_Title,                dgs(MSG_TITLE),
  167.             TAGIF(flags & IU_DISPLAY,AR_Button),    dgs(MSG_DISPLAY_GAD),
  168.             TAGIF(flags & IU_DISPLAY,AR_Button),    dgs(MSG_NODISPLAY_GAD),
  169.             TAGNOT(flags & IU_DISPLAY,AR_Button),    dgs(MSG_OK_GAD),
  170.             TAG_END);
  171.  
  172.         // Free memory allocated for buffer.
  173.         deleteResNode(&data->rnd,rn1);
  174.     }
  175.     return(iu_return);
  176. }
  177.  
  178. /*= Get_Files_To_Compare() ===========================================================-.
  179. || Fills in the file paths and names of the two files to be compared.                  ||
  180. || If two files are selected in the Source lister they will be chosen. Otherwise,      ||
  181. || the file selected in the Souce lister is chosen along with the first one in the      ||
  182. || Destination lister.                                                                  ||
  183. || Returns boolean success.                                                              ||
  184. || Does *not* report any errors (e.g. not enough selected files) to the user to be      ||
  185. || be in keeping with how the other Opus commands behave.                              ||
  186. `-====================================================================================*/
  187. BOOL get_files_to_compare(Compare_Data *data,char *path1,char *name1,\
  188.                                              char *path2,char *name2)
  189. {
  190.     struct path_node *pn1;
  191.     ResNode *rn1;
  192.     LONG src_entries;
  193.     BOOL gftc_return = FALSE;
  194.  
  195.     // Allocate a temporary path buffer.
  196.     if (rn1 = allocNewResNode(&data->rnd,PATHBUFFSIZE))
  197.     {
  198.         // path_node to pn1 and path string to ResNode's allocated buffer.
  199.         // If no source lister is returned silently abort. There is not much
  200.         // point in an error message because Opus won't let this function run
  201.         // without a source lister, so something really bad has happened.
  202.         if (pn1 = (struct path_node *)\
  203.             data->func_callback(EXTCMD_GET_SOURCE,IPCDATA(data->ipc),rn1->rn_Mem) )
  204.         {
  205.             // Store the handle of our lister for later use.
  206.             (data->listerhandle) = (pn1->lister);
  207.  
  208.             src_entries =\
  209.                 data->func_callback(EXTCMD_ENTRY_COUNT,IPCDATA(data->ipc),NULL);
  210.  
  211.             switch(src_entries)
  212.             {
  213.                 // If there are no files selected something really bad has
  214.                 // happened as Opus shouldn't have started this function at all.
  215.                 case 0:    break;
  216.  
  217.                 // If there's only one file selected we have to find the other
  218.                 // in the Destination lister.
  219.                 case 1:    gftc_return = get_s_and_d(data,rn1,path1,name1,path2,name2);
  220.                         break;
  221.  
  222.                 // If there's 2 or more selected, use the first two.
  223.                 default: gftc_return = get_s_twice(data,rn1,path1,name1,path2,name2);
  224.                          break;
  225.             }
  226.  
  227.             // Unless we're about to abort anyway, free up the destination
  228.             // lister, if there is one, as we don't need it anymore.
  229.             // (I really hate it when my dest listers get busied when they
  230.             // really don't need to be, especially when I intended to use them
  231.             // once my first operation was launched! grr!! :-) )
  232.             if (gftc_return)
  233.             {
  234.                 g_freedest(data,rn1);
  235.             }
  236.         }
  237.         // Free memory allocated for buffer.
  238.         deleteResNode(&data->rnd,rn1);
  239.     }
  240.     return(gftc_return);
  241. }
  242.  
  243. /*= get_s_twice() ====================================================================-.
  244. || Used by Get_Files_To_Compare() to get two file from the source lister.              ||
  245. || Returns boolean success.                                                              ||
  246. `-====================================================================================*/
  247. BOOL get_s_twice(Compare_Data *data,ResNode *rn1,char *path1,char *name1,
  248.                                                  char *path2,char *name2)
  249. {
  250.     BOOL gst_return = FALSE;
  251.     struct function_entry *fentry;
  252.     struct endentry_packet eep;
  253.  
  254.     // Put the source lister path at the front of both path strings.
  255.     strcpy(path1,rn1->rn_Mem);
  256.     strcpy(path2,rn1->rn_Mem);
  257.  
  258.     // Get first entry from source lister.
  259.     if (fentry = (struct function_entry *)\
  260.      data->func_callback(EXTCMD_GET_ENTRY,IPCDATA(data->ipc),NULL))
  261.     {
  262.         strcat(path1,fentry->name);
  263.         strcpy(name1,fentry->name);
  264.  
  265.         // Finish with the entry and deselect it.
  266.         eep.entry = fentry;
  267.         eep.deselect = TRUE;
  268.         data->func_callback(EXTCMD_END_ENTRY,IPCDATA(data->ipc),&eep);
  269.  
  270.         // Now get the second entry and do the same thing with it.
  271.         if (fentry = (struct function_entry *)\
  272.          data->func_callback(EXTCMD_GET_ENTRY,IPCDATA(data->ipc),NULL))
  273.         {
  274.             strcat(path2,fentry->name);
  275.             strcpy(name2,fentry->name);
  276.  
  277.             // Finish with the entry and deselect it.
  278.             eep.entry = fentry;
  279.             eep.deselect = TRUE;
  280.             data->func_callback(EXTCMD_END_ENTRY,IPCDATA(data->ipc),&eep);
  281.  
  282.             // We got our filenames/paths okay.
  283.             gst_return = TRUE;
  284.         }
  285.     }
  286.     return(gst_return);
  287. }
  288.  
  289. /*= get_s_and_d() ====================================================================-.
  290. || Used by Get_Files_To_Compare() to get one file from the source and one from the      ||
  291. || destination lister. Returns boolean success.                                          ||
  292. `-====================================================================================*/
  293. BOOL get_s_and_d(Compare_Data *data,ResNode *rn1,char *path1,char *name1,
  294.                                                  char *path2,char *name2)
  295. {
  296.     BOOL gsad_return = FALSE;
  297.     struct function_entry *fentry;
  298.     struct endentry_packet eep;
  299.     struct command_packet cp;
  300.     struct path_node *pn1;
  301.     ResNode *combufrn;
  302.  
  303.     // Allocate a temporary buffer to build ARexx commands in.
  304.     if (combufrn = allocNewResNode(&data->rnd,COMMBUFFSIZE))
  305.     {
  306.         // Put the source lister path at the front of the first path string.
  307.         strcpy(path1,rn1->rn_Mem);
  308.  
  309.         // Get entry from source lister.
  310.         if (fentry = (struct function_entry *)\
  311.                         data->func_callback(EXTCMD_GET_ENTRY,IPCDATA(data->ipc),NULL))
  312.         {
  313.             strcat(path1,fentry->name);
  314.             strcpy(name1,fentry->name);
  315.  
  316.             // Finish with the entry and deselect it.
  317.             eep.entry = fentry;
  318.             eep.deselect = TRUE;
  319.             data->func_callback(EXTCMD_END_ENTRY,IPCDATA(data->ipc),&eep);
  320.  
  321. /* There are not callback functions to deal with entries in the dest lister, so
  322.    we have to do it with ARexx. */
  323.  
  324.             // First, get the dest lister's handle.
  325.             // We no longer need the old contents of rn1->rn_Mem.
  326.             // If we can't get a dest, silently fail to be consistent with what
  327.             // Opus does internally.
  328.             if (pn1 = (struct path_node *)\
  329.                 data->func_callback(EXTCMD_GET_DEST,IPCDATA(data->ipc),rn1->rn_Mem) )
  330.             {
  331.                 // Second path starts with the dest lister path.
  332.                 strcpy(path2,rn1->rn_Mem);
  333.  
  334.                 // Get the first selected entry in the dest lister via ARexx.
  335.                 // Build ARexx command (insert lister handle).
  336.                 sprintf(combufrn->rn_Mem,"lister query %ld firstsel",pn1->lister);
  337.                 cp.command = combufrn->rn_Mem;
  338.                 // We do want a result.
  339.                 cp.flags = COMMANDF_RESULT;
  340.                 // Send the command.
  341.                 if ( data->func_callback(EXTCMD_SEND_COMMAND,IPCDATA(data->ipc),&cp) )
  342.                 {
  343.                     // If something wasn't selected abort silently.
  344.                     if (cp.result[0] == '\0')
  345.                         FreeVec(cp.result);
  346.                     else
  347.                     {
  348.                         // Copy the name on, but without the two quotes.
  349.                         strcat(path2,cp.result + 1);
  350.                         strcpy(name2,cp.result + 1);
  351.                         path2[strlen(path2)-1] = '\0';
  352.                         name2[strlen(name2)-1] = '\0';
  353.  
  354.                         // Now we just have to deselect the entry (via ARexx)
  355.                         // and free the string which Opus allocated for it.
  356.  
  357.                         // Filename already has quotes around it.
  358.                         sprintf(combufrn->rn_Mem,"lister select %ld %s 0",pn1->lister,
  359.                                                                     cp.result);
  360.                         FreeVec(cp.result);
  361.  
  362.                         data->func_callback(EXTCMD_SEND_COMMAND,IPCDATA(data->ipc),&cp);
  363.                         sendExtCmd_nr(data,combufrn->rn_Mem,&cp);
  364.  
  365.                         sprintf(combufrn->rn_Mem,"lister refresh %ld",pn1->lister);
  366.                         sendExtCmd_nr(data,combufrn->rn_Mem,&cp);
  367.  
  368.                         // We got our filenames/paths okay.
  369.                         gsad_return = TRUE;
  370.                     }
  371.                 }
  372.                 // Free up the destination lister.
  373.                 data->func_callback(EXTCMD_END_DEST,IPCDATA(data->ipc),&gsad_return);
  374.             }
  375.         }
  376.         // Free memory allocated for command buffer.
  377.         deleteResNode(&data->rnd,combufrn);
  378.     }
  379.     return(gsad_return);
  380. }
  381.  
  382. /*= get_freedest() ===================================================================-.
  383. || Used by Get_Files_To_Compare() to free-up the destination lister (if any) so that  ||
  384. || the user may use it during the compare.                                              ||
  385. `-====================================================================================*/
  386. void g_freedest(Compare_Data *data,ResNode *rn1)
  387. {
  388.     struct command_packet cp;
  389.     struct path_node *pn1;
  390.     ResNode *combufrn;
  391.  
  392.     // Allocate a temporary buffer to build ARexx commands in.
  393.     if (combufrn = allocNewResNode(&data->rnd,COMMBUFFSIZE))
  394.     {
  395.         // First, get the dest lister's handle, if there is one. (If there isn't
  396.         // there's nothing for us to do.) We no longer need the old contents of
  397.         // rn1->rn_Mem. We try twice because if we got a file from the dest lister,
  398.         // we'll have already gone through the dest-lister-list (there will only
  399.         // be the one lister because of how our flags are set) and the first try
  400.         // will return NULL.
  401.         if ( (pn1 = (struct path_node *)\
  402.               data->func_callback(EXTCMD_GET_DEST,IPCDATA(data->ipc),rn1->rn_Mem)) || \
  403.              (pn1 = (struct path_node *)\
  404.               data->func_callback(EXTCMD_GET_DEST,IPCDATA(data->ipc),rn1->rn_Mem)) )
  405.         {
  406.             sprintf(combufrn->rn_Mem,"lister set %ld busy off",pn1->lister);
  407.             sendExtCmd_nr(data,combufrn->rn_Mem,&cp);
  408.         }
  409.         deleteResNode(&data->rnd,combufrn);
  410.     }
  411. }
  412.  
  413. /*= open_files_to_compare() ==========================================================-.
  414. || Attempts to open the two files in preparation for comparison.                      ||
  415. || Returns boolean success and will fail if the files cannot be openned, or cannot be ||
  416. || be examined.                                                                          ||
  417. `-====================================================================================*/
  418. BOOL open_files_to_compare(Compare_Data *data)
  419. {
  420.     BOOL oftc_return = FALSE;
  421.  
  422.     // Give the ResNodes filenames.
  423.     (data->file1rn->rn_Name) = (data->file1path);
  424.     (data->file2rn->rn_Name) = (data->file2path);
  425.  
  426.     // Attempt to get the size of each file.
  427.     if ( !( (examineResNode(&data->rnd,data->file1rn)) && \
  428.             (examineResNode(&data->rnd,data->file2rn)) ) )
  429.     {
  430.         // Error message and abort if we couldn't get either size.
  431.         informUser(data,dgs(MSG_EXAMFAIL),TRUE,NULL);
  432.     }
  433.     else
  434.     {
  435.         if ((data->file1rn->rn_FIB->fib_Size) != (data->file2rn->rn_FIB->fib_Size))
  436.         {
  437.             (data->minsize) = min((data->file1rn->rn_FIB->fib_Size),
  438.                                   (data->file2rn->rn_FIB->fib_Size));
  439.  
  440.             // Warn them that the files are of different sizes and only the first
  441.             // xxx bytes will be compared.
  442.             informUser(data,dgs(MSG_DIFFSIZES_FMT),TRUE,NULL,data->minsize);
  443.         }
  444.         else
  445.             (data->minsize) = (data->file1rn->rn_FIB->fib_Size);
  446.  
  447.         if (!( (openFileResNode(&data->rnd,data->file1rn,MODE_OLDFILE))
  448.             && (openFileResNode(&data->rnd,data->file2rn,MODE_OLDFILE)) ))
  449.         {
  450.             // Error message and abort if we couldn't actually open either file.
  451.             informUser(data,dgs(MSG_OPENFAIL),TRUE,NULL);
  452.         }
  453.         else
  454.             oftc_return = TRUE;
  455.     }
  456.     return(oftc_return);
  457. }
  458.  
  459. /*= compare_files() ==================================================================-.
  460. || Actually compares the files which have been made ready by previous routines.          ||
  461. || Builds a linked list of DiffRange's which describe where the differences are.      ||
  462. || Returns the number of different bytes, or (-1) on failure/user-abort.              ||
  463. `-====================================================================================*/
  464. LONG compare_files(Compare_Data *data)
  465. {
  466.     LONG cf_return = -1;    // We return the number of different bytes, or -1 on error.
  467.     APTR progwin;    // Handle of progress window.
  468.     LONG remfsize;    // Bytes remaining to be compared.
  469.     LONG donebytes;    // Total bytes done.
  470.     LONG diffbytes;    // Total bytes different.
  471.     LONG dobytes;    // Bytes remaining in current loop.
  472.     BOOL comperror;    // If true there has been an error while comparing.
  473.     LONG infotxtid;    // Used for choosing one out of a set of locale strings.
  474.     LONG lastdiff;    // Offset from start of file to the last different byte.
  475.     LONG thisdiff;    // Offset from start of file to the current different byte.
  476.     LONG thisdiffstart;    // -._ Form an interval around the current
  477.     LONG thisdiffend;    // -'  different byte (offsets from start of file).
  478.     struct DiffRange *drlast;    // Last dr in linked list. (Base is data->drbase)
  479.     struct DiffRange *drnew;    // New/current DiffRange structure.
  480.     char *cp1;        // Pointer to current posn. in compare buffer1.
  481.     char *cp2;        // Pointer to current posn. in compare buffer2.
  482.     char *cpe;        // Pointer to end of compare buffer1.
  483.     char *cpo;        // Pointer to start of compare buffer1.
  484.     ResNode *progbrn;    // ResNode for memory allocated for building progress msg.
  485.  
  486.     remfsize = (data->minsize);
  487.  
  488.     // Open progress window.
  489.     if (progwin = OpenProgressWindowTags(\
  490.                 PW_Window,        getListerWindow(data),
  491.                 PW_Title,        dgs(MSG_PROGTITLE),
  492.                 PW_FileName,    data->file1name,
  493.                 PW_FileCount,    remfsize,
  494.                 PW_Flags,        PWF_INFO|PWF_GRAPH|PWF_ABORT|PWF_FILENAME,
  495.                 TAG_END))
  496.     {
  497.         // Allocate comparison buffers. Error message and abort if we can't.
  498.         if (!(allocMemResNode(&data->rnd,data->file1rn,COMPAREBUFFSIZE) && \
  499.               allocMemResNode(&data->rnd,data->file2rn,COMPAREBUFFSIZE) && \
  500.               (progbrn = allocNewResNode(&data->rnd,NAMEBUFFSIZE+50)) ))
  501.         {
  502.             informUser(data,dgs(MSG_CMPBUFFAIL),TRUE,NULL);
  503.         }
  504.         else
  505.         {
  506.             // Initialize some variables (remfsize initialized above).
  507.             (data->drbase) = drlast = NULL;
  508.             // Make sure first difference starts a new new DiffRange
  509.             lastdiff = (-(4*DR_EXTEND));
  510.             donebytes = diffbytes = 0;
  511.             comperror = FALSE;
  512.  
  513.             while( (remfsize > 0) && (!(CheckProgressAbort(progwin))) && \
  514.                  (!(data->func_callback(EXTCMD_CHECK_ABORT,IPCDATA(data->ipc),NULL))) \
  515.                  && (!comperror) )
  516.             {
  517.                 // Use a different string depending on how many bytes are different.
  518.                 // No crappy "xxx error(s)" messages -- using the correct
  519.                 // singular/plural form is quick'n'easy and not doing so is just lazy.
  520.                 switch (diffbytes)
  521.                 {
  522.                     case 0:        infotxtid = MSG_DIFFPROG0_FMT; break;
  523.                     case 1:        infotxtid = MSG_DIFFPROG1_FMT; break;
  524.                     default:    infotxtid = MSG_DIFFPROGP_FMT; break;
  525.                 }
  526.                 sprintf(progbrn->rn_Mem,dgs(infotxtid),data->file2name,diffbytes);
  527.                 SetProgressWindowTags(progwin,
  528.                     PW_FileNum,donebytes,
  529.                     PW_Info,progbrn->rn_Mem,
  530.                     TAG_END);
  531.  
  532.                 // If we filled the compare buffers, compare the entire buffers,
  533.                 // otherwise compare until the end of the file.
  534.                 dobytes = ((remfsize < COMPAREBUFFSIZE) ? remfsize : COMPAREBUFFSIZE);
  535.  
  536.                 // Update the number of bytes remaining to be compared after this
  537.                 // block is done. remfsize will go <= 0 when we're done.
  538.                 remfsize -= COMPAREBUFFSIZE;
  539.  
  540.                 // Read the data for the next comparison from the two files.
  541.                 if ((0>=Read(data->file1rn->rn_FHandle,data->file1rn->rn_Mem,dobytes))\
  542.                  || (0>=Read(data->file2rn->rn_FHandle,data->file2rn->rn_Mem,dobytes)))
  543.                 {
  544.                     // If we couldn't read for some reason, report the error
  545.                     // and flag that we should abort.
  546.                     comperror = TRUE;
  547.                     informUser(data,dgs(MSG_READFAIL),TRUE,NULL);
  548.                 }
  549.  
  550.                 // Setup the buffer pointers.
  551.                 cp1 = (data->file1rn->rn_Mem);
  552.                 cp2 = (data->file2rn->rn_Mem);
  553.                 cpo = cp1;
  554.                 cpe = cp1 + dobytes;
  555.  
  556.                 // While there isn't an error, compare the two buffers, storing
  557.                 // the ranges in which differences occur.
  558.  
  559.                 for (; (!comperror) && (cp1 < cpe); cp1++,cp2++)
  560.                 {
  561.                     if ((*cp1) != (*cp2))
  562.                     {
  563.                         diffbytes++;
  564.                         thisdiff = (cp1 - cpo) + donebytes;
  565.  
  566.                         // We don't have to worry about pointing outside the
  567.                         // file here because the routine which deals with the
  568.                         // DiffRanges handles that.
  569.                         thisdiffstart = thisdiff - (thisdiff%8);
  570.                         thisdiffend = thisdiff + ((8 - (thisdiff%8)) - 1);
  571.  
  572.                         // If this difference is within 3 * DR_EXTEND bytes of
  573.                         // the previous one, just extend the previous difference range.
  574.                         // Otherwise, create a new difference range of just this byte.
  575.  
  576.                         if ((thisdiffend - lastdiff) <= (3*DR_EXTEND))
  577.                             (drlast->end) = thisdiffend;
  578.                         else
  579.                         {
  580.                             // We don't bother tracking the allocations of the
  581.                             // Difference Ranges: they'll be freed when the pool is
  582.                             // destroyed and don't need to be freed before that.
  583.                             if (!(drnew = AllocMemH(data->rnd.poolhead,
  584.                                             sizeof(struct DiffRange)) ))
  585.                             {
  586.                                 comperror = TRUE;
  587.                                 informUser(data,dgs(MSG_OUTOFMEM),TRUE,NULL);
  588.                                 DisplayBeep(NULL);
  589.                             }
  590.                             else
  591.                             {
  592.                                 if (!(data->drbase))
  593.                                     (data->drbase) = drlast = drnew;
  594.                                 else
  595.                                 {
  596.                                     (drlast->next) = drnew;
  597.                                     drlast = drnew;
  598.                                 }
  599.                                 (drnew->start) = thisdiffstart;
  600.                                 (drnew->end) = thisdiffend;
  601.                             }
  602.                         }
  603.                         lastdiff = (thisdiffend + 1);
  604.                     }
  605.                 }
  606.                 donebytes += dobytes;
  607.             }
  608.  
  609.             // Only signal that the rest of the program should continue if there
  610.             // wasn't an error and the user didn't abort before the end of the files.
  611.             if ((!comperror) && (remfsize <= 0))
  612.             {
  613.                 cf_return = diffbytes;
  614.  
  615.                 // Make sure the progress window reaches 100% done.
  616.                 switch (diffbytes)
  617.                 {
  618.                     case 0:        infotxtid = MSG_DIFFPROG0_FMT; break;
  619.                     case 1:        infotxtid = MSG_DIFFPROG1_FMT; break;
  620.                     default:    infotxtid = MSG_DIFFPROGP_FMT; break;
  621.                 }
  622.                 sprintf(progbrn->rn_Mem,dgs(infotxtid),data->file2name,diffbytes);
  623.                 SetProgressWindowTags(progwin,
  624.                     PW_FileNum,donebytes,
  625.                     PW_Info,progbrn->rn_Mem,
  626.                     TAG_END);
  627.             }
  628.  
  629.             // Free memory for the comparison buffers.
  630.             deleteResNode(&data->rnd,progbrn);
  631.             freeMemResNode(&data->rnd,data->file1rn);
  632.             freeMemResNode(&data->rnd,data->file2rn);
  633.         }
  634.         CloseProgressWindow(progwin);
  635.     }
  636.     return(cf_return);
  637. }
  638.  
  639. /*= report_num_diffs() ===============================================================-.
  640. || Reports the number of differences to the user and gives them the option of a full  ||
  641. || display of them. Returns TRUE if they do, FALSE if they don't. (Note that they      ||
  642. || cannot ask for a full display when the files are identical.)                          ||
  643. `-====================================================================================*/
  644. BOOL report_num_diffs(Compare_Data *data,LONG diffbytes)
  645. {
  646.     BOOL rnd_return = FALSE;
  647.  
  648.     switch (diffbytes)
  649.     {
  650.         case 0:
  651.             rnd_return = FALSE;
  652.             informUser(data,dgs(MSG_NUMDIFFS0),TRUE,NULL);
  653.             break;
  654.         case 1:
  655.             rnd_return = \
  656.                     informUser(data,dgs(MSG_NUMDIFFS1_FMT),TRUE,IU_DISPLAY,diffbytes);
  657.             break;
  658.         default:
  659.             rnd_return = \
  660.                 informUser(data,dgs(MSG_NUMDIFFSP_FMT),TRUE,IU_DISPLAY,diffbytes);
  661.             break;
  662.     }
  663.     return(rnd_return);
  664. }
  665.  
  666. /*= getListerWindow() ================================================================-.
  667. || Gets the window of a lister from just the lister handle (in ASCII).                  ||
  668. || Returns the handle, or NULL. This should not be stored for later use as the window ||
  669. || may be different next time (for example, Opus has reopenned on another screen).      ||
  670. || This is a bit of a hack, but Jonathan Potter has said it should be okay.              ||
  671. `-====================================================================================*/
  672. struct Window *getListerWindow(Compare_Data *data)
  673. {
  674.     struct path_node pn;
  675.  
  676.     // Setup some semi-sensible defaults (and hope they'll do!)
  677.     pn.buffer[0] = '\0';
  678.     pn.path = pn.buffer;
  679.     pn.flags = NULL;
  680.  
  681.     pn.lister = (data->listerhandle);
  682.  
  683.     return((struct Window *)\
  684.                 data->func_callback(EXTCMD_GET_WINDOW,IPCDATA(data->ipc),(APTR)&pn));
  685. }
  686.  
  687. /*= getDOpusScreen() =================================================================-.
  688. || Attempts to get the Opus screen, returns it or NULL.                                  ||
  689. || The return should not be stored for later use as the screen may be different next  ||
  690. || time if the user has changed it.                                                      ||
  691. `-====================================================================================*/
  692. struct Screen *getDOpusScreen(Compare_Data *data)
  693. {
  694.     struct Screen *screen = NULL;
  695.     struct DOpusScreenData *dsd;
  696.  
  697.     if (dsd = (struct DOpusScreenData *)\
  698.                 data->func_callback(EXTCMD_GET_SCREENDATA,IPCDATA(data->ipc),NULL))
  699.     {
  700.         screen = dsd->screen;
  701.  
  702.         data->func_callback(EXTCMD_FREE_SCREENDATA,IPCDATA(data->ipc),(APTR)dsd);
  703.     }
  704.     return(screen);
  705. }
  706.  
  707. /*= report_full_display() ============================================================-.
  708. || Based on the linked list of DiffRanges produced by compare_files(), this routine      ||
  709. || builds a side-by-side Hex/ASCII dump of the differences between the two files.      ||
  710. ||------------------------------------------------------------------------------------||
  711. || Nasty long routine alert!                                                          ||
  712. ||------------------------------------------------------------------------------------||
  713. || Unlike the comparison routine, there is no fixed buffer size for this routine: it  ||
  714. || will attempt to allocate enough memory to hold two versions of each DiffRange.      ||
  715. || Only one DiffRange is dealt with at a time, but it's always possible that every      ||
  716. || byte is different and the files are large. This is hardly a concern as:              ||
  717. || a) If there are millions of differences it's unlikely that the user will want to      ||
  718. ||      see them all. ("Duh, It's no case of just a different version string, Bob,      ||
  719. ||      these files really are different and the filesize is coincidence!").              ||
  720. || b) The report file goes to T: and the DOpus text viewer loads it all at once, and  ||
  721. ||      the report file is several times larger than any one DiffRange in the set which ||
  722. ||      produces it, so if there isn't enough memory for a DiffRange there isn't going  ||
  723. ||      to be enough to display the final report anyway. (And I'm not about to rewrite  ||
  724. ||      the Opus text viewer or faff about with offering destinations other than T:,      ||
  725. ||      given than point (a) is pretty stand-alone anyway.)                              ||
  726. || If you're a complete psycho, feel free to think this is a bad way of doing it, be  ||
  727. || my guest and recode it. Then see if it makes the blindest bit of difference in      ||
  728. || The Real World(tm).                                                                  ||
  729. || (Anyone would think I was feeling inadequate, or something...) Ahh-hu-hu-huh-hem...||
  730. `-====================================================================================*/
  731. void report_full_display(Compare_Data *data)
  732. {
  733.     LONG errkeep;
  734.     struct DiffRange *drnew;    // New/current DiffRange structure.
  735.     ResNode *dislinern;
  736.     ResNode *tempfilern;
  737.     ResNode *hexbuf1rn;
  738.     ResNode *hexbuf2rn;
  739.     ResNode *ascbuf1rn;
  740.     ResNode *ascbuf2rn;
  741.     BPTR outfile;            // -.
  742.     char *disline;            //  |_ Copies of things from ResNodes
  743.     char *f1block;            //  |  for shorter/simpler lines of code.
  744.     char *f2block;            // -'
  745.     LONG donebytes;            // How far into the file we are. (start of current DiffRng)
  746.     LONG dobytes;            // How many bytes to do in the current DiffRange.
  747.     APTR progwin;
  748.     ULONG tsecs;            // -._ Used to get a unique filename
  749.     ULONG tmics;            // -'  for the tempfile in T:.
  750.     ResNode *combufrn;
  751.     struct command_packet cp;
  752.  
  753.     // Open progress window.
  754.     if (progwin = OpenProgressWindowTags(\
  755.                 PW_Window,        getListerWindow(data),
  756.                 PW_Title,        dgs(MSG_PROGTITLE),
  757.                 PW_FileName,    dgs(MSG_GENERATING),
  758.                 PW_FileCount,    data->minsize,
  759.                 PW_Flags,        PWF_GRAPH|PWF_ABORT|PWF_FILENAME,
  760.                 TAG_END))
  761.     {
  762.         if ( (dislinern = allocNewResNode(&data->rnd,DISPLAYLINEBUFFSIZE)) && \
  763.              (hexbuf1rn = allocNewResNode(&data->rnd,HEXBUFFSIZE)) && \
  764.              (hexbuf2rn = allocNewResNode(&data->rnd,HEXBUFFSIZE)) && \
  765.              (ascbuf1rn = allocNewResNode(&data->rnd,ASCBUFFSIZE)) && \
  766.              (ascbuf2rn = allocNewResNode(&data->rnd,ASCBUFFSIZE)) && \
  767.              (tempfilern = allocNewResNode(&data->rnd,PATHBUFFSIZE)) )
  768.         {
  769.             disline = (dislinern->rn_Mem);
  770.             donebytes = 0;
  771.  
  772.             SetProgressWindowTags(progwin,
  773.                 PW_FileNum,donebytes,
  774.                 TAG_END);
  775.  
  776.             // Generate a unique filename. Virtually impossible to get two reports
  777.             // from the same lister within one second of each other. Using the micros
  778.             // value in the filename runs the risk of generating a name over 30 chars.
  779.             CurrentTime(&tsecs,&tmics);
  780.             sprintf(tempfilern->rn_Mem,"t:cmp_%lu_%lu.tmp",data->listerhandle,tsecs);
  781.             (tempfilern->rn_Name) = (tempfilern->rn_Mem);
  782.  
  783.             // This is the line identifying which file is on which side of the output.
  784.             sprintf(disline,dgs(MSG_LEFTRIGHT),data->file1path,data->file2path);
  785.  
  786.             // Attempt to open the temp file for writting to.
  787.             outfile = openFileResNode(&data->rnd,tempfilern,MODE_NEWFILE);
  788.  
  789.             // Flag that the file should be deleted automatically.
  790.             // (This flag will be removed if the file gets to the Opus viewer
  791.             // successfully as it will be left to Opus to delete it after we
  792.             // have exited.) -- Safe to set flag even if file didn't open.
  793.             (tempfilern->rn_TempFile) = TRUE;
  794.  
  795.             // Now check the open and write the header line.
  796.             if (!( (outfile) && (0 < Write(outfile,disline,strlen(disline))) ))
  797.             {
  798.                 // If we couldn't open or write to the file, error message & abort.
  799.                 informUser(data,dgs(MSG_ERRWRITE),TRUE,NULL);
  800.                 outfile = NULL;        // Signal to abort.
  801.             }
  802.             else
  803.             {
  804.                 // Now output the differences dump for each DiffRange in turn.
  805.                 // If outfile goes NULL it will abort the loop.
  806.  
  807.                 for(drnew = (data->drbase); (outfile) && (drnew); drnew = drnew->next)
  808.                 {
  809.                     // Include DR_EXTENT bytes either side of the interval,
  810.                     // but make sure we don't try to read outside of the file.
  811.                     // (The DiffRange may already point outside the file, but we'll
  812.                     // also fix that here anyway.)
  813.  
  814.                     if (0 > ((drnew->start) -= DR_EXTEND))
  815.                         (drnew->start) = 0;
  816.                     if ((data->minsize) <= ((drnew->end)+=DR_EXTEND))
  817.                         (drnew->end) = ((data->minsize)-1);
  818.  
  819.                     // Set number of bytes done (also start offset in file).
  820.                     donebytes = (drnew->start);
  821.  
  822.                     // Calculate the interval (block) size and allocate two buffers
  823.                     // associated with each file.
  824.  
  825.                     dobytes = ( ((drnew->end)-(drnew->start)) + 1 );
  826.  
  827.                     if (!( (f1block = (char *)\
  828.                                 allocMemResNode(&data->rnd,data->file1rn,dobytes)) && \
  829.                            (f2block = (char *)\
  830.                                 allocMemResNode(&data->rnd,data->file2rn,dobytes)) ))
  831.                     {
  832.                         // Free the file1 memory if it got allocated.
  833.                         freeMemResNode(&data->rnd,data->file1rn);
  834.  
  835.                         // Close & delete output file to free as much mem as we can.
  836.                         deleteResNode(&data->rnd,tempfilern);
  837.                         tempfilern = NULL;    // Make safe the delete attempt below.
  838.                         outfile = NULL;        // Signal to abort.
  839.                         informUser(data,dgs(MSG_OUTOFMEM),TRUE,NULL);
  840.                     }
  841.                     else
  842.                     {
  843.                         Seek(data->file1rn->rn_FHandle,drnew->start,OFFSET_BEGINNING);
  844.                         errkeep = IoErr();
  845.                         Seek(data->file2rn->rn_FHandle,drnew->start,OFFSET_BEGINNING);
  846.                         errkeep = (errkeep | IoErr());
  847.  
  848.                         if (errkeep)
  849.                         {
  850.                             // Free the two buffers to get as much mem as we can.
  851.                             freeMemResNode(&data->rnd,data->file1rn);
  852.                             freeMemResNode(&data->rnd,data->file2rn);
  853.  
  854.                             // Close & delete temp file to free as much mem as we can.
  855.                             deleteResNode(&data->rnd,tempfilern);
  856.                             tempfilern = NULL;    // Make safe the delete attempt below.
  857.                             outfile = NULL;        // Signal to abort.
  858.                             informUser(data,dgs(MSG_READFAIL),TRUE,NULL);
  859.                         }
  860.                         else
  861.                         {
  862.                             // Attempt to read the files into the buffers.
  863.                             if ((0 >= Read(data->file1rn->rn_FHandle,f1block,dobytes))\
  864.                              || (0 >= Read(data->file2rn->rn_FHandle,f2block,dobytes)))
  865.                             {
  866.                                 // Free the two buffers to get as much mem as we can.
  867.                                 freeMemResNode(&data->rnd,data->file1rn);
  868.                                 freeMemResNode(&data->rnd,data->file2rn);
  869.  
  870.                                 // Close & delete temp file to free as much mem as can.
  871.                                 deleteResNode(&data->rnd,tempfilern);
  872.                                 tempfilern = NULL;    // Make safe the delete below.
  873.                                 outfile = NULL;        // Signal to abort.
  874.                                 informUser(data,dgs(MSG_READFAIL),TRUE,NULL);
  875.                             }
  876.                             else
  877.                             {
  878.                                 if (!(outrepline(data,outfile,f1block,f2block,dobytes,
  879.                                                  donebytes,disline,
  880.                                                  hexbuf1rn->rn_Mem,hexbuf2rn->rn_Mem,
  881.                                                  ascbuf1rn->rn_Mem,ascbuf2rn->rn_Mem)))
  882.                                 {
  883.                                     // Free the two buffers to get as much mem as can.
  884.                                     freeMemResNode(&data->rnd,data->file1rn);
  885.                                     freeMemResNode(&data->rnd,data->file2rn);
  886.  
  887.                                     // Close & delete temp file to free as much mem.
  888.                                     deleteResNode(&data->rnd,tempfilern);
  889.                                     tempfilern = NULL;    // Make safe the delete below.
  890.                                     outfile = NULL;        // Signal to abort.
  891.                                     informUser(data,dgs(MSG_ERRWRITE),TRUE,NULL);
  892.                                 }
  893.                                 else
  894.                                 {
  895.                                     SetProgressWindowTags(progwin,
  896.                                         PW_FileNum,donebytes,
  897.                                         TAG_END);
  898.                                 }
  899.                             }
  900.                         }
  901.                         // Free the two buffers ready for the next interval (block).
  902.                         // Safe to call if mem already freed.
  903.                         freeMemResNode(&data->rnd,data->file1rn);
  904.                         freeMemResNode(&data->rnd,data->file2rn);
  905.                     }
  906.                 }
  907.  
  908.                 // If the file is still open there wasn't an error.
  909.                 if (outfile)
  910.                 {
  911.                     // Make sure the progress window gets to 100%
  912.                     SetProgressWindowTags(progwin,
  913.                         PW_FileNum,data->minsize,
  914.                         TAG_END);
  915.  
  916.                     // Stop the file being deleted automatically when the ResNode is.
  917.                     // We're going to run "dopus read delete <filename>"
  918.                     // asynchronously and let Opus delete the file when it's finished.
  919.                     (tempfilern->rn_TempFile) = FALSE;
  920.  
  921.                     // We cannot delete the tempfilern yet as it still contains the
  922.                     // filename. We have to close the file, though.
  923.  
  924.                     closeFileResNode(&data->rnd,tempfilern);
  925.                     outfile = NULL;
  926.  
  927.                     // Show it to the user.
  928.                     if (combufrn = allocNewResNode(&data->rnd,COMMBUFFSIZE))
  929.                     {
  930.                         sprintf(combufrn->rn_Mem,"dopus read delete %s",
  931.                                 tempfilern->rn_Name);
  932.                         sendExtCmd_nr(data,combufrn->rn_Mem,&cp);
  933.  
  934.                         deleteResNode(&data->rnd,combufrn);
  935.                         combufrn = NULL;
  936.                     }
  937.                 }
  938.             }
  939.             // These ResNode pointers must be valid or NULL -- if they get
  940.             // deleted in some error handling code above the code must also NULL
  941.             // the relevante ResNode pointer as well.
  942.             deleteResNode(&data->rnd,tempfilern);
  943.             deleteResNode(&data->rnd,dislinern);
  944.             deleteResNode(&data->rnd,hexbuf1rn);
  945.             deleteResNode(&data->rnd,hexbuf2rn);
  946.             deleteResNode(&data->rnd,ascbuf1rn);
  947.             deleteResNode(&data->rnd,ascbuf2rn);
  948.         }
  949.         CloseProgressWindow(progwin);
  950.     }
  951. }
  952.  
  953. /*= outrepline() =====================================================================-.
  954. || Writes the actual side-by-side hex dump to the output file of the full display.      ||
  955. || Returns boolean success, Doesn't attempt to clean anything up on failure, that's      ||
  956. || left to the calling routine to keep things simpler.                                  ||
  957. || Doesn't send error messages, caller should free buffers and report the error.      ||
  958. `-====================================================================================*/
  959. BOOL outrepline(Compare_Data *data,BPTR outfile,char *f1block,char *f2block,\
  960.                 LONG dobytes, LONG donebytes,char *disline,char *hex1,char *hex2,
  961.                 char *asc1,char *asc2)
  962. {
  963.     BOOL orl_return = TRUE;
  964.     char *disp;
  965.     char *h1;
  966.     char *h2;
  967.     char *a1;
  968.     char *a2;
  969.     BOOL indif;        // When true, the inverse-ANSI code is 'active'.
  970.     int i,j;
  971.  
  972.     while( (orl_return) && (dobytes > 0) )
  973.     {
  974.  
  975. /*- Generate the various components in hex1,hex2,asc1,asc2... ------------------------*/
  976.  
  977.         h1 = hex1;        // -.
  978.         h2 = hex2;        //  |_ We use these new pointers while building the string.
  979.         a1 = asc1;        //  |  Need to keep the old pointers to the begginings.
  980.         a2 = asc2;        // -'
  981.  
  982.         indif = FALSE;
  983.  
  984.         for (j = 0; j < 2; j++)
  985.         {
  986.             for (i = 0; i < 4; i++)
  987.             {
  988.                 if (dobytes > 0)
  989.                 {
  990.                     if ( (*f1block) == (*f2block) )
  991.                     {
  992.                         if (indif)
  993.                         {
  994.                             h1 = stpcpy(h1,"");
  995.                             h2 = stpcpy(h2,"");
  996.                             a1 = stpcpy(a1,"");
  997.                             a2 = stpcpy(a2,"");
  998.                             indif = FALSE;
  999.                         }
  1000.                         sprintf(h1,"%02x",(int)(*f1block));
  1001.                         h2 = stpcpy(h2,h1);
  1002.                         h1 += 2;
  1003.                         (*(a1++)) = (*(a2++)) = printchar(*f1block);
  1004.                     }
  1005.                     else
  1006.                     {
  1007.                         if (!(indif))
  1008.                         {
  1009.                             h1 = stpcpy(h1,"");
  1010.                             h2 = stpcpy(h2,"");
  1011.                             a1 = stpcpy(a1,"");
  1012.                             a2 = stpcpy(a2,"");
  1013.                             indif = TRUE;
  1014.                         }
  1015.                         sprintf(h1,"%02x",(int)(*f1block));
  1016.                         h1 += 2;
  1017.                         sprintf(h2,"%02x",(int)(*f2block));
  1018.                         h2 += 2;
  1019.                         (*(a1++)) = printchar(*f1block);
  1020.                         (*(a2++)) = printchar(*f2block);
  1021.                     }
  1022.  
  1023.                     f1block++;
  1024.                     f2block++;
  1025.                     dobytes--;
  1026.                 }
  1027.                 else
  1028.                 {
  1029.                     if (indif)
  1030.                     {
  1031.                         h1 = stpcpy(h1,"");
  1032.                         h2 = stpcpy(h2,"");
  1033.                         a1 = stpcpy(a1,"");
  1034.                         a2 = stpcpy(a2,"");
  1035.                         indif = FALSE;
  1036.                     }
  1037.                     (*(h1++)) = (*(h2++)) = (*(a1++)) = (*(a2++)) = ' ';
  1038.                     (*(h1++)) = (*(h2++)) = ' ';
  1039.                 }
  1040.             }
  1041.  
  1042.             // Make sure the ANSI codes are reset at the end of the strings.
  1043.             if (indif)
  1044.             {
  1045.                 h1 = stpcpy(h1,"");
  1046.                 h2 = stpcpy(h2,"");
  1047.                 a1 = stpcpy(a1,"");
  1048.                 a2 = stpcpy(a2,"");
  1049.                 indif = FALSE;
  1050.             }
  1051.  
  1052.             // Always add a space after the hex strings.
  1053.             // If we've just done the second four (out of eight), NULL terminate all
  1054.             // strings.
  1055.  
  1056.             (*(h1++)) = (*(h2++)) = ' ';
  1057.             if (i > 0)
  1058.             {
  1059.                 (*h1) = (*h2) = (*a1) = (*a2) = '\0';
  1060.             }
  1061.         }
  1062.  
  1063. /*- Join the various pieces together into one line... --------------------------------*/
  1064.  
  1065.         // Offset.
  1066.         sprintf(disline,"%08lx | ",donebytes);
  1067.         disp = (disline + 11);    // Point to just after "89ABCDEF: "
  1068.  
  1069.         donebytes += 8;
  1070.  
  1071.         disp = stpcpy(disp,hex1);            // Left hex dump.
  1072.         disp = stpcpy(disp,asc1);            // Left ASCII dump.
  1073.         disp = stpcpy(disp," | ");            // Divider.
  1074.         disp = stpcpy(disp,hex2);            // Right hex dump.
  1075.         disp = stpcpy(disp,asc2);            // Right ASCII dump.
  1076.                stpcpy(disp,"\n");            // End of line.
  1077.  
  1078.         // Write to the output.
  1079.         if (0 >= Write(outfile,disline,strlen(disline)))
  1080.         {
  1081.             orl_return = FALSE;        // Flag failure (Abort).
  1082.         }
  1083.     }
  1084.  
  1085.     if (orl_return)
  1086.     {
  1087.         if (0 >= Write(outfile,"\n",strlen("\n")))
  1088.         {
  1089.             orl_return = FALSE;        // Flag failure (Abort).
  1090.         }
  1091.     }
  1092.  
  1093.     return(orl_return);
  1094. }
  1095.  
  1096. /*= sendExtCmd_nr() ==================================================================-.
  1097. || Function to make calling Opus ARexx commands slightly cleaner. This version for      ||
  1098. || when you do not want a result string.                                              ||
  1099. `-====================================================================================*/
  1100. void sendExtCmd_nr(Compare_Data *data,char *cmdstring,struct command_packet *cpp)
  1101. {
  1102.     // We do NOT want a result, but dopus5.library seems prone to
  1103.     // memory leaks when one isn't requested for certain commands,
  1104.     // so we'll ask for one and free it immediately afterwards.
  1105.     cpp->flags = COMMANDF_RESULT;
  1106.     cpp->command = cmdstring;
  1107.     data->func_callback(EXTCMD_SEND_COMMAND,IPCDATA(data->ipc),cpp);
  1108.     FreeVec(cpp->result);
  1109.     cpp->result = NULL;
  1110. }
  1111.